package xyz.scootaloo.kami.server.service.impl

import cn.hutool.core.codec.Base64
import cn.hutool.core.util.CharsetUtil
import cn.hutool.core.util.RandomUtil
import cn.hutool.core.util.StrUtil
import cn.hutool.crypto.asymmetric.KeyType
import cn.hutool.crypto.asymmetric.RSA
import cn.hutool.crypto.digest.MD5
import io.vertx.core.Future
import io.vertx.core.Future.failedFuture
import io.vertx.core.Future.succeededFuture
import xyz.scootaloo.kami.server.standard.CacheInvalidException
import xyz.scootaloo.kami.server.service.CacheService
import xyz.scootaloo.kami.server.service.TokenService
import xyz.scootaloo.kami.server.service.TokenService.EncodeToken

/**
 * @author flutterdash@qq.com
 * @since 2022/2/4 16:19
 */
object InternalTokenServiceImpl : TokenService {

    private val md5 = MD5()

    private val cache = CacheService()

    override fun generateToken(clientId: String, expiryTime: Long): EncodeToken {
        val rsa = RSA()
        val token = generateRandomString()
        val storeKey = getStoreKey(clientId, token)
        storeKeyValuePair(storeKey, rsa.privateKeyBase64, expiryTime)
        return EncodeToken(token, rsa.publicKeyBase64)
    }

    override fun decodeContent(clientId: String, token: String, base64encoded: String): Future<String> {
        val storeKey = getStoreKey(clientId, token)
        val encoded = Base64.decode(base64encoded)
        return cache.getString(storeKey).transform { done ->
            if (done.succeeded()) {
                val privateKey = done.result()
                if (privateKey != null) {
                    try {
                        val rsa = RSA(privateKey, null)
                        val decoded = StrUtil.str(
                            rsa.decrypt(encoded, KeyType.PrivateKey),
                            CharsetUtil.CHARSET_UTF_8
                        )
                        succeededFuture(decoded)
                    } catch (e: Throwable) {
                        failedFuture(e)
                    }
                } else {
                    failedFuture(CacheInvalidException(storeKey))
                }
            } else {
                failedFuture(done.cause())
            }
        }
    }

    override fun generateRandomString(len: Int): String {
        return RandomUtil.randomString(len)
    }

    override fun encodePasswordBySalt(password: String, salt: String): String {
        return md5.digestHex("$password$salt")
    }

    override fun matchPassword(password: String, encodedPassword: String, salt: String): Boolean {
        return md5.digestHex("$password$salt") == encodedPassword
    }

    private fun getStoreKey(clientId: String, token: String): String {
        return "token:${clientId}:${token}"
    }

    private fun storeKeyValuePair(key: String, value: String, expiryTime: Long) {
        cache.putString(key, value, expiryTime)
    }
}